/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the ViewMyCurrency extension.
 *
 * The Initial Developer of the Original Code is
 * Will Moffat.
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

//dump("// $Id: WILL_ViewMyCurrency.js,v 1.75 2007/02/28 11:18:31 Will Exp $;\n");

VMC.ViewMyCurrency = function() {
  var self=this;

  //public functions
  this.toggleHideOriginal           = toggleHideOriginal;
  this.getTargetCurrency            = getTargetCurrency;
  this.getNextTargetCurrency        = getNextTargetCurrency;
  this.changeToNextTargetCurrency   = changeToNextTargetCurrency;
  this.disableConversion            = disableConversion;
  this.updateCurrentTab             = updateCurrentTab;
  this.die                          = die;
  this.getRateFunc                  = null; // pointer to exchangeRateCache.getRate //WILL: make private but needed by VMC.PopupConvertToolbarManager ?
  this.observe                      = observe;
  this.updateGuiCallback            = function() { VMC.log('ERR','null callback - ViewMyCurrencyGui did not initialise'); }; 
  this.prefChangedCallBack          = prefChangedCallBack;

  // public vars
  this.prefManager   = null;
  this.exchangeRateCache = null;
  this.tooltipMode = false;
  this.conversionProfileTable = null;

  // private vars:
  this._targetCurrency     = null;
  this._targetCurrencyList = null;
  this._sourceCurrencyList = null;
  this._delayToNextWebUpdate = 30*1000; // normally we run update after 30 sec. This gives user time to start using Firefox without delay added by me. This is only used for the initial web update. (Currency the only other webupdates that run are forced by the user in Options dialog)

  //private funcs
  this._onDOMContentLoaded           = _onDOMContentLoaded;
  this._setTargetCurrency            = _setTargetCurrency;
  this._scheduleInitialWebUpdate     = _scheduleInitialWebUpdate;
  this._delayed_initialWebUpdate     = _delayed_initialWebUpdate;
  this._scheduleCall                 = _scheduleCall;
  this._processFrame                 = _processFrame;
  this._initBeforeInstall            = _initBeforeInstall;
  this._initAfterInstall             = _initAfterInstall;
  this._updateProfile                = _updateProfile;

  this._initBeforeInstall();

  function _initBeforeInstall() {
    
    this.prefManager   = new VMC.PrefManager().init(); // Two-stage init required for Firefox 1.0
    this.prefChangedCallBack(VMC.PrefManager.keys.DEBUG_LEVEL); //initalise debug level by faking a pref change

    this._targetCurrencyList = new VMC.CurrencyList(); 
    this._sourceCurrencyList = new VMC.CurrencyList(); 

    var exCache = new VMC.ExchangeRateCache(this.prefManager.readRate, this.prefManager.writeRate,
					     this._sourceCurrencyList,this._targetCurrencyList);
    this.exchangeRateCache = exCache;
    this.getRateFunc = function(from,to) { return exCache.getRate(from,to); }; //WILL: is this really cleaner than just exposing the whole cache object
  }

  //after delay milliseconds, calls this.funcName(null) 
  //WILL: TODO: why can't I just used setTimeout?
  function _scheduleCall(funcName,delay) {
    var info='Scheduling '+funcName+' in '+delay/1000+'s';
    VMC.log('INFO',info);
    if (this._delayedCall) { VMC.log('ERR','There is already a delayed call running!'); }
    this._delayedCall = new VMC.CCallWrapper(this, delay, funcName); 
    VMC.CCallWrapper.asyncExecute(this._delayedCall);
  }



    
  function _initAfterInstall(firstEverRun) {
    var keys=VMC.PrefManager.keys;

    var interestingKeys = [keys.DEBUG_LEVEL,
			   // WILL: target list must be updated before source because we trigger updates using targetCurrencyList. 
			   keys.TARGET_CURRENCY_LIST, keys.SOURCE_CURRENCY_LIST, 
			   keys.HIDE_ORIGINAL_CURRENCY, keys.ROUND_NUMBERS,
			   keys.DECIMAL_SEPARATOR, keys.THOUSANDS_SEPARATOR, keys.FORCE_SPACE, keys.FORCE_SYMBOL_POS, keys.HIGHLIGHT_CHANGES,
			   keys.TOOLTIP_MODE,keys.LAYOUT_ORDER, keys.TARGET_CURRENCY_CODE, keys.RATE ];

    this.prefManager.registerWatcher(this,interestingKeys,'prefChangedCallBack');
  
    this.exchangeRateCache.webUpdater.addObserver(this);
  
    // activate initalise settings that have been read from prefs by faking pref changes
    for (var i=0; i<interestingKeys.length; i++) {
      var key=interestingKeys[i];
      if (key==keys.RATE) { continue; } // RATE is a branch, not an individual pref. The rates will be updated when the currency lists changes
      this.prefChangedCallBack(key, true); // skipUpdateBrowser = true     
    }

    if (firstEverRun) {
      this._delayed_initialWebUpdate(); //run the update immediately
    } else {
      this._scheduleInitialWebUpdate(); // run after a short delay - improves Firefox response and copes with failures
    }

    this.updateCurrentTab(); // after initalising from the pregs, finally refresh the page
    this.updateGuiCallback(); // and the VMC GUI
  }



  function _scheduleInitialWebUpdate(immediately) {
    var delay;
    if (immediately) {
      delay=500; // well ok, not immediately, but after half a second. Gives this a chance to be cancelled by following pref updates
      if (this._delayedCall) { 
	this._delayedCall.cancel();
	this._delayedCall=null;
	VMC.log('INFO','(Had to cancel pending call)');
      }
    } else {
      delay=this._delayToNextWebUpdate;
      this._delayToNextWebUpdate *= 2; // double the back-off delay
    }
    this._scheduleCall('_delayed_initialWebUpdate',delay);
  }

  function _delayed_initialWebUpdate() {
    VMC.log('DBG','Doing delayed web update');
    this._delayedCall=null; // delete the call wrapper
    this.exchangeRateCache.updateFromWeb(false,true); //try to update any old or missing rates. forceUpdate=false, topOnFirstFailure=true
    //WILL: TODO: updates are now only on application startup - this should be periodic check
  }

  function observe(status) {
    //WILL: TODO: should I consider resetting update_interval if we get a END_OK? It doesn't matter for now, because we never update again.
    switch(status) {
    case 'END_OK' : this.updateCurrentTab();  break; // the exchange rates have been successfully updated, now we update the current web pages.
    case 'END_ERRORS' : this._scheduleInitialWebUpdate(); break; // try updating again after a delay.
    default : // ignore all the rest (GUI takes care of updating gui)
    }
  } 

  function _updateProfile() {
//     var excludeRegexps = 
//     [ /^chrome:\/\/(?!jsunit)/, //no chrome unless chrome://jsunit
//       /^https/, // no banking sites
//       /^http:\/\/viewmycurrency/ // no VMC sites, it gets too confusing!
//       ];

//     var excludedEntry = new VMC.ConversionProfileTableEntry('ExcludedSites',excludeRegexps, null);  // null profile

//     var symbolTable = VMC.currencyDB.generateDefaultSymbolTableFor(this._sourceCurrencyList);
//     var conversionProfile = new VMC.ConversionProfile(symbolTable);
//     var defaultEntry = new VMC.ConversionProfileTableEntry('Default', null, conversionProfile); // null regexps (match everything)
//     var table = new VMC.ConversionProfileTable( [excludedEntry, defaultEntry] );

    //HACK: this.prefManager.writeTableFile(table);
    var table = this.prefManager.getDefaultConversionTable(); //readTableFile();     //HACK: what to do with code above??

    this.conversionProfileTable = table;
  }

  function prefChangedCallBack(prefName,skipUpdateBrowser) {
    VMC.log('DBG','pref changed: '+prefName);

    var list=false; // did a currency list change?

    var keys=VMC.PrefManager.keys;

    switch(prefName) {
      // thse do not need a page refresh
    case keys.HIDE_IN_STATUSBAR       : return; // The GUI should respond to this
    case keys.DEBUG_LEVEL             : VMC.debugLevel=this.prefManager.getDebugLevel(); return; 
    case keys.TOOLTIP_MODE            : return; // The GUI should respond to this

      // these require special list processing
    case keys.SOURCE_CURRENCY_LIST    : this.prefManager.readSourceCurrencyList(this._sourceCurrencyList); this._updateProfile(); list = true; break;
    case keys.TARGET_CURRENCY_LIST    : this.prefManager.readTargetCurrencyList(this._targetCurrencyList); list = true; break;

      // the page will be refreshed
    case keys.HIDE_ORIGINAL_CURRENCY  : this.hideOriginalCurrency  = this.prefManager.getHideOriginalCurrency(); break;
    case keys.ROUND_NUMBERS           : VMC.Number.prototype.performRounding    = this.prefManager.getRoundNumbers(); break;
    case keys.DECIMAL_SEPARATOR       : VMC.Number.prototype.decimalSeparator   = this.prefManager.getDecimalSeparator(); break;
    case keys.THOUSANDS_SEPARATOR     : VMC.Number.prototype.thousandsSeparator = this.prefManager.getThousandsSeparator(); break;
    case keys.FORCE_SPACE             : VMC.NumberConversion.prototype.forceSpaceBetweenNumberAndSymbol = this.prefManager.getForceSpace(); break;
    case keys.LAYOUT_ORDER            : VMC.NumberConversion.prototype.targetInBrackets = this.prefManager.getLayoutOrder(); break;
    case keys.FORCE_SYMBOL_POS        : VMC.NumberConversion.prototype.forceSymbolPosition = this.prefManager.getForceSymbolPos(); break;
    case keys.HIGHLIGHT_CHANGES       : VMC.HtmlConversion.highlightChanges = this.prefManager.getHighlightChanges(); break; //WILL: TODO: need to force an update of nodes to see the effect. Could disableConversion and then re-enable
    case keys.TARGET_CURRENCY_CODE    : this._targetCurrency = this._targetCurrencyList.findFromCode(this.prefManager.getTargetCurrencyCode()); break;

    default : 
      if (prefName.match(keys.RATE)) {
	var allRatesSet = this.exchangeRateCache.setAllRatesFromPrefs(); //WILL: TODO: should I modify setAllRates to return false also if rates are old?
      }
      else { VMC.log('ERR','Saw unexpected pref '+prefName+' change'); return; }
    }
    
    if (list) { // did a list change?
      this.exchangeRateCache.sourceOrTargetListChanged();
      if (!skipUpdateBrowser) { //we delay initial webupdate
	this._scheduleInitialWebUpdate(true); //do it now!
      }

     //WILL: we can't set the target currency (which redoes the page) until we've updated the rates. 
     //update the target currency - if it's not on the list anymore, go back to null.
      this._setTargetCurrency(this._targetCurrencyList.findFromCode(this.prefManager.getTargetCurrencyCode())); //WILL: TODO: check this!
      // ( _setTargetCurrency calls updateCurrentTab )
    } else {

      if (!skipUpdateBrowser) { // only need to redraw pages if we are currency converting
	if (this._targetCurrency || prefName==keys.TARGET_CURRENCY_CODE) {
	  // refresh page if we are currently converting. Always refresh if target changed (restore page if target=null)
	  this.updateGuiCallback();  // gui must change if targetCurrency or hideOriginal changed. FUTURE: also if there were errors to show.
	  this.updateCurrentTab(); // we make the gui change first and then do the work. It looks more responsive

	}
      }
    }

  }


  //////////////
  function die() {
    //////////////
    // we got an unload event

    if (VMC.CCallWrapper.mPendingCalls.length) {
      this._delayedCall.cancel();
    } else {
      this.exchangeRateCache.webUpdater.removeObserver(this);
    }

    this.prefManager.unregisterWatcher(this); //kills all watchers. Included that of GUI
  }

  function toggleHideOriginal() {
    this.hideOriginalCurrency = ! this.hideOriginalCurrency;
    this.prefManager.setHideOriginalCurrency(this.hideOriginalCurrency);
    //_watchHideOriginal will take care of updating browsers
  }

  function getTargetCurrency()     {  return this._targetCurrency;  }
  function getNextTargetCurrency() {  return this._targetCurrencyList.getNext(this._targetCurrency);  }
  function changeToNextTargetCurrency() { this._setTargetCurrency(this.getNextTargetCurrency());     }
  function disableConversion()          { this._setTargetCurrency(null);                             }

  //update the target currency - if it's not on the list go to disabled
  function _setTargetCurrency(newTargetCurrency) {
    var newTarget=null;
    if (newTargetCurrency) {
      if (this._targetCurrencyList.existsInList(newTargetCurrency)) { newTarget=newTargetCurrency; }
      else { VMC.log('INFO','Setting targetCurrency to null as '+newTargetCurrency+' not in '+this._targetCurrencyList); }
    }
    if (this._targetCurrency != newTarget) {
      if (!newTarget) { this.updateGuiCallback("OFF")   } // give instant visual cue that we are going OFF (prefs can take .5 sec to update?)
      // we are changing currency
      this.prefManager.setTargetCurrencyCode( newTarget ? newTarget.code : null);
      //rely on the pref change to do this.updateGuiCallback(); this._targetCurrency=newTarget; to this.updateCurrentTab();
    }
  }

  //WILL: TODO: there is some duplication of code between updateCurrentTab and _onDOMContentLoaded
  function updateCurrentTab() {
    if (this.tooltipMode) {return; }

    var activeProfileEntry = VMC.HtmlConversion.getActiveProfileEntry(gBrowser.selectedBrowser.contentDocument, this.conversionProfileTable);
    var profileText = activeProfileEntry.description;
    this.updateGuiCallback("PROFILE",profileText); // WILL: TODO: code overlap with _onDOMContentLoaded

    //WILL: we use a ConvertObserver to collect the different statuses. There are multiple tabs, probably with multiple frames.
    //WILL: The ConvertObserver decides what final status we return to the GUI.
    
    var convertObserver;
    if (this._targetCurrency) { 
      var updateGuiCallback = this.updateGuiCallback;
      convertObserver = {
	statusToReport : null,
	outstandingFramesToConvert : 0,
	notify : function(newStatus) {
	  this.outstandingFramesToConvert--;
	  if (!this.statusToReport || this.statusToReport == 'END_NO_CHNG') {
	    this.statusToReport = newStatus; 
	  }
	  //else 'AVOID' or 'END_CHANGES' are not overwritten
	  VMC.log('DBG','outstandingFramesToConvert='+this.outstandingFramesToConvert+' statusToReport='+this.statusToReport);
	  if (this.outstandingFramesToConvert==0) {
	    gBrowser.selectedBrowser.VMC_convertedStatus = this.statusToReport;
	    updateGuiCallback(this.statusToReport);
	  }
	}
      };
      this.updateGuiCallback("START"); // show visual cue that we are starting to convert.
    } // else targetCurrency is OFF, and we don't monitor progress of restore page
    var browser = gBrowser.selectedBrowser;
    browser.VMC_convertedTo = this._targetCurrency;
    this._processFrame(browser.contentWindow,convertObserver);
  }

  function _processFrame(f,convertObserver) {
    if (!f) {return; }
    VMC.log('DBG','Processing frame '+f+' location='+f.location);
    
    if (this._targetCurrency) {
      convertObserver.outstandingFramesToConvert++;
      var notifyFunc = function(newStatus) { convertObserver.notify(newStatus); };
      VMC.HtmlConversion.changePageToTargetCurrencyUsingNodes(f.document, this._targetCurrency, this.hideOriginalCurrency, this.conversionProfileTable, this.getRateFunc, notifyFunc); 
    } else {
      VMC.HtmlConversion.restorePage(f.document,this.conversionProfileTable);
    }
    
    for (var i=0;i<f.frames.length; i++) {
      this._processFrame(f.frames[i],convertObserver);
    }
  }



  // happens every time I load a page

  ////////////////////////
  function _onDOMContentLoaded(e) { //WILL: maybe this should be moved to the VMC GUI?
    ////////////////////////
    var document = e.originalTarget;
    if (document.defaultView.top != gBrowser.selectedBrowser.contentWindow ) {
      return; //frame is not in current tab
    }

    var activeProfileEntry = VMC.HtmlConversion.getActiveProfileEntry(document, this.conversionProfileTable);
    var profileText = activeProfileEntry.description;
    this.updateGuiCallback("PROFILE",profileText); // WILL: TODO: consider what happens with frames. This will be call multiple times?


    if (this.tooltipMode) { return; }

    var startTime=new Date();


    gBrowser.selectedBrowser.VMC_convertedTo = this._targetCurrency;

    if (this._targetCurrency) {
      this.updateGuiCallback("START"); // show visual cue that we are starting
      VMC.HtmlConversion.changePageToTargetCurrencyUsingNodes(document, this._targetCurrency, this.hideOriginalCurrency,
							      this.conversionProfileTable, this.getRateFunc, this.updateGuiCallback);
    }

    var endTime=new Date();
    VMC.log('INFO','Extra delay due to me was '+ (endTime-startTime) + 'ms');
  }

};
